package wumpus;

import java.util.*;

/** Rules:

    Wumpuses (alive or dead) emit a smell that can be detected from adjacent squares.

    Pits cause a breeze that can be detected from adjacent squares.

    Gold causes a glimmer that can be detected from a square.

    The goal of the agent is to safely find the gold and leave the
    maze (which requires returning to (0,0)). The agent can also end
    the game prematurely (without the gold), but they must be in (0,0)
    to do so.
**/
public class WumpusState
{
    public static final int FLAG_WUMPUS = 1, FLAG_DEAD_WUMPUS = 2, FLAG_PIT = 4, FLAG_GOLD = 8;

    public static final int POINTS_MOVE = -2;
    public static final int POINTS_GOLD = 100;
    public static final int POINTS_DIE = -20;
    public static final int POINTS_WUMPUS = 30;

    /** Invariant: height = board.length; width=board[i].length; **/
    public int width, height;

    /** Each board cell is interpreted according to the flags
     * above. Indexed by y, then x. Note that (0,0) is the bottom left
     * / south-west corner.**/
    public int board[][];

    /** Position of the agent. **/
    public int agentx, agenty;

    /** Number of arrows left. **/
    public int narrows = 1;

    /** How many points have been scored? **/
    public int score;

    public boolean gameOver = false;
    public boolean gotGold = false;
    public boolean dead = false;

    public WumpusState(int seed, int sz, int npits)
    {
        width = sz;
        height = sz;
        board = new int[height][width];

        Random r = new Random(seed);

        // place pits.
        for (int i = 0; i < npits; i++) {
            while (true) {
                int px = r.nextInt(width);
                int py = r.nextInt(height);

                if ((px == 0 && py == 0) || hasPit(px, py))
                    continue;

                board[py][px] |= FLAG_PIT;
                break;
            }
        }

        // place wumpi
        for (int i = 0; i < 1; i++) {
            while (true) {
                int wx = r.nextInt(width);
                int wy = r.nextInt(height);

                if ((wx == 0 && wy == 0) || hasLiveWumpus(wx, wy))
                    continue;

                board[wy][wx] |= FLAG_WUMPUS;
                break;
            }
        }

        // place gold (wumpus cell is okay, but not pit.)
        while (true) {
            int gx = r.nextInt(width);
            int gy = r.nextInt(height);

            if ((gx == 0 && gy == 0) || hasPit(gx, gy))
                continue;

            board[gy][gx] |= FLAG_GOLD;
            break;
        }
    }

    public void performMove(String move)
    {
        String toks[] = move.toUpperCase().trim().split("\\s+");
        if (toks[0].equals("MOVE")) {
            if (toks.length == 3) {
                move(Integer.parseInt(toks[1]), Integer.parseInt(toks[2]));
            } else if (toks[1].equals("N"))
                move(0, 1);
            else if (toks[1].equals("S"))
                move(0, -1);
            else if (toks[1].equals("E"))
                move(1, 0);
            else if (toks[1].equals("W"))
                move(-1, 0);
            else {
                System.out.println("Illegal move: "+move);
                return;
            }
        } else if (toks[0].equals("SHOOT")) {
            if (toks.length == 3) {
                shoot(Integer.parseInt(toks[1]), Integer.parseInt(toks[2]));
            } else if (toks[1].equals("N"))
                shoot(0, 1);
            else if (toks[1].equals("S"))
                shoot(0, -1);
            else if (toks[1].equals("E"))
                shoot(1, 0);
            else if (toks[1].equals("W"))
                shoot(-1, 0);
            else {
                System.out.println("Illegal move: "+move);
                return;
            }
        } else if (toks[0].equals("GRAB")) {
            grab();
        } else if (toks[0].equals("SUICIDE")) {
            suicide();
        } else if (toks[0].equals("LEAVE")) {
            leave();
        } else {
            System.out.println("Illegal move: "+move);
            return;
        }
    }

    protected boolean hasNeighbor(int x, int y, int mask)
    {
        boolean v = false;

        if (x-1 >= 0)
            v |= ((board[y][x-1] & mask) != 0);

        if (x+1 < width)
            v |= ((board[y][x+1] & mask) != 0);

        if (y-1 >= 0)
            v |= ((board[y-1][x] & mask) != 0);

        if (y+1 < height)
            v |= ((board[y+1][x] & mask) != 0);

        return v;
    }

    public boolean isBreezy(int x, int y)
    {
        return hasNeighbor(x, y, FLAG_PIT);
    }

    public boolean isSmelly(int x, int y)
    {
        return hasNeighbor(x, y, (FLAG_WUMPUS | FLAG_DEAD_WUMPUS));
    }

    public boolean hasPit(int x, int y)
    {
        return (board[y][x]&FLAG_PIT)!=0;
    }

    public boolean hasLiveWumpus(int x, int y)
    {
        return (board[y][x]&FLAG_WUMPUS)!=0;
    }

    public boolean hasDeadWumpus(int x, int y)
    {
        return (board[y][x]&FLAG_DEAD_WUMPUS)!=0;
    }

    public boolean hasGold(int x, int y)
    {
        return (board[y][x]&FLAG_GOLD)!=0;
    }

    public boolean hasAgent(int x, int y)
    {
        return agentx == x && agenty==y;
    }

    /** Only allowed to move N, S, E, or W. **/
    public void move(int dx, int dy)
    {
        if (Math.abs(dx) + Math.abs(dy) != 1) {
            System.out.printf("Illegal move!\n");
            return;
        }

        if (gameOver)
            return;

        if (agentx+dx < 0 || agentx+dx >= width || agenty+dy < 0 || agenty+dy >= height) {
            System.out.printf("WARNING: Agent moved out of dungeon; committing suicide\n");
            suicide();
            return;
        }

        agentx += dx;
        agenty += dy;
        score += POINTS_MOVE;

        if (hasLiveWumpus(agentx, agenty) || hasPit(agentx, agenty)) {
            score += POINTS_DIE;
            dead = true;
            gameOver = true;
        }
    }

    public int getBoard(int x, int y)
    {
        return board[y][x];
    }

    /** Only allowed to shoot N, S, E, or W. **/
    public void shoot(int dx, int dy)
    {
        if (Math.abs(dx) + Math.abs(dy) != 1) {
            System.out.printf("Illegal move!\n");
            return;
        }

        if (gameOver)
            return;

        score += POINTS_MOVE;

        if (agentx+dx < 0 || agentx+dx >= width || agenty+dy < 0 || agenty+dy >= height) {
            System.out.printf("WARNING: Agent shot outside of dungeon.\n");
            return;
        }

        if (narrows == 0) {
            System.out.printf("WARNING: Your quiver is empty.\n");
            return;
        }

        if (narrows > 0) {
            narrows--;

            if (hasLiveWumpus(agentx+dx, agenty+dy)) {
                board[agenty+dy][agentx+dx] &= (~FLAG_WUMPUS);
                board[agenty+dy][agentx+dx] |= FLAG_DEAD_WUMPUS;
                score += POINTS_WUMPUS;
            }
        }
    }

    public void grab()
    {
        if (gameOver)
            return;

        score += POINTS_MOVE;

        if (hasGold(agentx, agenty)) {
            board[agenty][agentx] &= (~FLAG_GOLD);
            gotGold = true;
        }
    }

    /** End a game by leaving the dungeon; agent must be in (0,0) for
     * this to work! **/
    public void leave()
    {
        score += POINTS_MOVE;

        if (agentx == 0 && agenty == 0) {
            gameOver = true;

            if (gotGold)
                score += POINTS_GOLD;
        }
    }

    public void suicide()
    {
        score += POINTS_MOVE;
        score += POINTS_DIE;

        dead = true;
        gameOver = true;
    }

    public boolean isGameOver()
    {
        return gameOver;
    }

    // 'D' dead agent
    // 'A' agent with gold
    // 'a' agent without gold
    // 'W' wumpus
    // 'P' pit.
    // '.' nothing (but breezy/smelly per game rules)
    public void print()
    {
        for (int y = height-1; y >= 0; y--) {
            for (int x = 0; x < width; x++) {
                if (agentx==x && agenty==y) {
                    if (dead)
                        System.out.printf("D");
                    else if (gotGold)
                        System.out.printf("A");
                    else
                        System.out.printf("a");
                } else if (hasLiveWumpus(x,y) || hasDeadWumpus(x,y)) {
                    System.out.printf("W");
                } else if (hasPit(x,y)) {
                    System.out.printf("P");
                } else if (hasGold(x,y)) {
                    System.out.printf("G");
                } else {
                    System.out.printf(".");
                }
            }
            System.out.printf("\n");
        }
        System.out.printf("score: %4d\n", score);
    }
}
